Original Note

Python CI with GitHub Actions(整理版) - Read

Python CI with GitHub Actions(整理版)

原始资料: Building and testing Python created: 2026-07-02 16:58 整理说明: 本版本结合原始笔记和 GitHub Docs 原始教程重排、补全和翻译,保留常用英文术语;原教程截图已下载到本地附件目录。

YAML 常用字段速查

字段 常见写法 作用
name name: Python package workflow 在 GitHub Actions 页面中的名称。
run-name run-name: test on ${{ github.ref }} 单次 workflow run 的显示名。
on on: [push, pull_request] 定义触发 workflow 的事件。
permissions contents: read 限制 GITHUB_TOKEN 权限,发布或 OIDC 时尤其重要。
jobs jobs: build: workflow 中的 job 集合。
<job_id> build: job 的内部 ID,可以被 needs 引用。
runs-on ubuntu-latest 指定 job 运行的 runner。
strategy.matrix python-version: ["3.11", "3.12"] 为不同版本、系统或配置生成多组 job。
matrix.exclude exclude: [{ os: windows-latest, python-version: "3.11" }] 从 matrix 中排除不想运行的组合。
steps steps: job 内顺序执行的步骤列表。
uses actions/checkout@v6 调用已有 action。
with python-version: "3.x" 给 action 传入参数。
run pytest tests/ 在 runner shell 中执行命令。
env PYTHONWARNINGS: error 设置环境变量。
needs needs: build 指定 job 依赖关系。
if if: ${{ always() }} 控制 step 或 job 是否执行。
continue-on-error true 允许某个 step 失败但不中断整个 job。
timeout-minutes 10 限制 job 或 step 最长运行时间。

Python CI 常用字段速查

场景 推荐字段或命令 作用
checkout 代码 uses: actions/checkout@v6 把仓库代码拉到 runner。
设置 Python uses: actions/setup-python@v5 选择 CPython 或 PyPy,并加入 PATH
固定 Python 版本 python-version: "3.12" 使用明确版本,避免 runner 默认版本变化。
使用版本范围 python-version: "3.x" 获取最新的 Python 3 minor release。
多版本测试 python-version: ["3.9", "3.11", "3.13"] 通过 matrix 覆盖多个 Python 版本。
测 PyPy python-version: "pypy3.10" 验证项目在 PyPy 解释器上的兼容性。
指定架构 architecture: "x64" 设置解释器架构,默认通常是 x64
pip 缓存 cache: "pip" setup-python 缓存依赖,加速 CI。
依赖文件缓存键 cache-dependency-path: requirements.txt 指定依赖锁文件或需求文件。
安装基础构建工具 python -m pip install --upgrade pip setuptools wheel 更新 Python packaging 相关基础工具。
安装项目依赖 pip install -r requirements.txt 安装项目运行和测试依赖。
pytest 测试 pytest tests/ 运行测试套件。
覆盖率 pytest --cov=<package> --cov-report=xml 生成 coverage 报告。
JUnit 报告 --junitxml=junit/test-results.xml 生成可上传或分析的测试结果。
Ruff lint ruff check --output-format=github 在 GitHub UI 中显示 lint annotation。
Ruff format check ruff format --diff 检查格式差异。
tox tox -e py 用 tox 管理测试环境,适合复杂项目。
上传测试结果 uses: actions/upload-artifact@v4 保存 JUnit、coverage、日志等产物。
PyPI 发布 pypa/gh-action-pypi-publish CI 通过后发布 Python package。

内容简要概括

这篇笔记整理了如何用 GitHub Actions 为 Python 项目创建 CI workflow,覆盖从模板创建、选择 Python/PyPy 版本、安装依赖,到运行 pytest、Ruff、tox 和上传测试产物的常见做法。核心原则是用 actions/setup-python 明确指定 Python 版本,并用 strategy.matrix 在多个 Python 版本或操作系统上重复执行同一套测试。对 Python package 项目,还可以在 CI 通过后构建 artifact,并通过 Trusted Publishing 发布到 PyPI。

GitHub ActionsPython CIYAMLactions/setup-pythonactions/checkoutstrategy.matrixpython-versionPyPypiprequirements.txtpytestpytest-covRufftoxartifact

目录


1. Python CI 的整体流程

Python CI 的基本目标是:每次 push 或 PR 发生时,自动创建干净的 runner 环境,安装项目依赖,运行测试和静态检查,并输出可追踪的结果。

一个典型 Python CI workflow 包含这些步骤:

  1. actions/checkout 拉取仓库代码。
  2. actions/setup-python 指定 Python 或 PyPy 版本。
  3. 升级 pip,安装 setuptoolswheel、项目依赖和测试工具。
  4. 运行 pytestRufftox 等检查。
  5. 需要时上传 JUnit XML、coverage HTML/XML、日志或构建产物。
  6. 对 package 项目,可以在 release 事件后构建分发包并发布到 PyPI。

2. 使用 Python workflow template

GitHub 提供了 Python workflow template。如果仓库里已经有至少一个 .py 文件,GitHub 通常会推荐 Python 相关模板。

操作路径:

  1. 打开 GitHub repository 首页。
  2. 点击仓库顶部导航中的 Actions

Actions tab highlighted

  1. 如果仓库已经有 workflow,点击 New workflow
  2. Choose a workflow 页面搜索 Python application
  3. Python application workflow 上点击 Configure
  4. 按项目需要修改 workflow,例如 Python 版本、依赖安装命令、测试命令。
  5. 点击 Commit changes,GitHub 会把 python-app.yml 加入 .github/workflows 目录。

模板适合快速起步;如果项目需要多 Python 版本、多 OS、coverage、Ruff 或 PyPI 发布,通常要继续定制。

3. 指定 Python 版本

GitHub-hosted runners 自带工具缓存,其中包含 Python 和 PyPy。推荐使用 actions/setup-python,因为它会从 runner 的 tools cache 中查找指定版本,并把对应解释器加入 PATH。如果目标版本不在缓存中,setup-python 会按 action 规则下载并设置合适版本。

不要依赖 runner 默认 Python 版本。默认版本会随 runner 镜像变化而变化,可能让 CI 在未来某天突然表现不一致。

3.1 为什么要加 PyPy

PyPy 是 Python 的另一种解释器实现,和常见的 CPython 不同。它使用 JIT 编译策略,某些长时间运行的纯 Python 程序可能更快。

在 CI 中加入 PyPy 的价值主要是兼容性检查:

  • 验证代码是否依赖了 CPython 特有行为。
  • 发现 C extension、二进制依赖或运行时假设带来的差异。
  • 对宣称支持 PyPy 的 library/package,提供真实测试保障。

如果项目大量依赖只支持 CPython 的扩展包,或者根本不打算支持 PyPy,就不必强行加入 PyPy matrix。

3.2 多 Python 版本测试

多版本测试适合 library、package 或需要支持多个 Python 版本的项目。

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"

字段解释:

字段 说明
strategy.matrix.python-version 定义要测试的 Python/PyPy 版本列表。
${{ matrix.python-version }} 在每个 job 变体中读取当前 Python 版本。
actions/setup-python@v5 安装并激活当前 matrix 指定的 Python。
Display Python version 打印实际解释器版本,便于确认 CI 环境。

3.3 指定单个 Python 版本

单版本 CI 适合应用项目、课程作业或只需要保证当前主版本稳定的项目。

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.x"
          architecture: "x64"
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"

字段解释:

字段 说明
python-version: "3.x" 使用最新 Python 3 minor release;也可以写成 "3.12" 这类精确版本。
architecture: "x64" 指定解释器架构,通常可以省略,因为默认就是 x64
uses: actions/setup-python@v5 这里的 v5 是 action 版本,不是 Python 版本。

3.4 排除特定 matrix 组合

当某些 OS 和 Python 版本组合不需要测试,或者已知暂时不支持时,可以用 exclude 排除。

name: Python package

on: [push]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.9", "3.11", "3.13", "pypy3.10"]
        exclude:
          - os: macos-latest
            python-version: "3.11"
          - os: windows-latest
            python-version: "3.11"

    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"

字段解释:

字段 说明
matrix.os 定义多个 runner 系统。
runs-on: ${{ matrix.os }} 每个 matrix 变体使用对应系统运行。
exclude 删除某些不需要的 matrix 组合。

4. 安装依赖与缓存

GitHub-hosted runners 已经安装了 pip,但通常仍建议先升级 packaging 相关工具。

steps:
  - uses: actions/checkout@v6
  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version: "3.x"
  - name: Install dependencies
    run: python -m pip install --upgrade pip setuptools wheel

如果项目使用 requirements.txt

steps:
  - uses: actions/checkout@v6
  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version: "3.x"
      cache: "pip"
  - name: Install dependencies
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt

字段解释:

字段 说明
python -m pip 确保调用的是当前 Python 解释器对应的 pip
pip install -r requirements.txt 按项目依赖文件安装依赖。
cache: "pip" setup-python 自动缓存 pip 依赖。
cache-dependency-path 依赖文件不在默认位置时,用它指定路径。

如果需要更细粒度控制缓存,可以使用 actions/cache。不过对常见 Python 项目,先用 setup-python 自带的 cache: "pip" 更简单。

5. 测试、lint 与格式检查

5.1 使用 pytest 和 pytest-cov

steps:
  - uses: actions/checkout@v6
  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version: "3.x"
      cache: "pip"
  - name: Install dependencies
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt
      pip install pytest pytest-cov
  - name: Test with pytest
    run: |
      pytest tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=your_package --cov-report=xml --cov-report=html

字段解释:

字段 说明
pytest tests/ 运行 tests/ 目录下的测试。
--doctest-modules 同时检查 docstring 中的 doctest。
--junitxml=... 输出 JUnit XML,方便上传和集成测试报告。
--cov=your_package 指定要统计覆盖率的 package。
--cov-report=xml 生成 Cobertura 兼容 XML 覆盖率报告。
--cov-report=html 生成 HTML 覆盖率报告,适合上传为 artifact。

5.2 使用 Ruff 做 lint 和 format check

steps:
  - uses: actions/checkout@v6
  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version: "3.x"
  - name: Install Ruff
    run: pipx install ruff
  - name: Lint code with Ruff
    run: ruff check --output-format=github --target-version=py39
  - name: Check code formatting with Ruff
    run: ruff format --diff --target-version=py39
    continue-on-error: true

continue-on-error: true 适合在刚引入格式检查时使用:它会显示格式差异,但不会让整个 workflow 失败。等代码格式修好后,可以移除这个选项,让 CI 真正阻止新的格式问题。

5.3 使用 tox

tox 适合依赖、测试命令或环境矩阵比较复杂的项目。GitHub Actions 中可以把 Python 版本交给 matrix 管理,再让 tox -e py 使用当前 PATH 中的 Python。

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python: ["3.9", "3.11", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
      - name: Install tox
        run: pip install tox
      - name: Run tox
        run: tox -e py

6. 保存测试结果和发布到 PyPI

6.1 上传测试结果 artifact

测试失败时也可能需要保留测试报告,所以上传 artifact 的 step 常配合 if: ${{ always() }} 使用。

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest
      - name: Test with pytest
        run: pytest tests.py --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml
      - name: Upload pytest test results
        uses: actions/upload-artifact@v4
        with:
          name: pytest-results-${{ matrix.python-version }}
          path: junit/test-results-${{ matrix.python-version }}.xml
        if: ${{ always() }}

6.2 发布到 PyPI

发布到 PyPI 不应该和普通 push CI 混在一起。更常见的做法是在 GitHub release 发布时触发,并使用 PyPI Trusted Publishing,避免手动保存 API token。

name: Upload Python Package

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  release-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"
      - name: Build release distributions
        run: |
          python -m pip install build
          python -m build
      - name: Upload distributions
        uses: actions/upload-artifact@v4
        with:
          name: release-dists
          path: dist/

  pypi-publish:
    runs-on: ubuntu-latest
    needs: release-build
    permissions:
      id-token: write
    environment:
      name: pypi
    steps:
      - name: Retrieve release distributions
        uses: actions/download-artifact@v5
        with:
          name: release-dists
          path: dist/
      - name: Publish release distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

关键点:

  • needs: release-build 表示发布 job 必须等构建 job 成功后再执行。
  • permissions.id-token: write 是 Trusted Publishing 所需权限。
  • environment: pypi 可以配合 GitHub environment protection 做发布保护。
  • 生产级 workflow 建议 pin action 到 commit SHA,降低上游 action 被改动带来的供应链风险。

7. 原始教程要点

GitHub Docs 的 Python CI 教程主要强调:

  • Python workflow template 可以快速生成 .github/workflows/python-app.yml
  • GitHub-hosted runners 自带 Python、PyPy 和 pip,但 CI 中仍应显式使用 actions/setup-python
  • 多版本测试通过 strategy.matrix 实现,既可以覆盖多个 Python 版本,也可以覆盖多个 OS。
  • setup-python 支持 pip 缓存,默认会查找 requirements.txtPipfile.lockpoetry.lock 等依赖文件。
  • 测试命令可以复用本地命令,例如 pytestrufftox
  • 测试报告、coverage、日志和截图等都可以用 artifact 保存,便于失败后排查。
  • 发布到 PyPI 时优先考虑 Trusted Publishing,不要在仓库中硬编码或提交 API token。

8. 可复用模板汇总

8.1 推荐起步版 Python CI

name: Python CI

on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest pytest-cov
      - name: Test
        run: |
          pytest tests/ --junitxml=junit/test-results.xml --cov=your_package --cov-report=xml --cov-report=html
      - name: Upload test results
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.python-version }}
          path: |
            junit/
            htmlcov/
            coverage.xml
        if: ${{ always() }}

8.2 加 Ruff 的版本

name: Python CI

on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest pytest-cov ruff
      - name: Lint
        run: ruff check --output-format=github .
      - name: Format check
        run: ruff format --check .
      - name: Test
        run: pytest tests/